package com.opcode.nebulamobil.base.services;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;

import com.opcode.nebulamobil.base.interfaces.NebulaSearchListener;
import com.opcode.nebulamobil.information.ApplicationInfo;
import com.opcode.nebulamobil.information.dataforms.costadataforms.JSON;
import com.opcode.nebulamobil.information.requests.costarequests.ActivationReq;
import com.opcode.nebulamobil.information.requests.costarequests.AppInfoReq;
import com.opcode.nebulamobil.information.responses.JSONResponse;
import com.opcode.nebulamobil.information.responses.costaresponses.ActivationResp;
import com.opcode.nebulamobil.information.responses.costaresponses.AppInfoResp;
import com.opcode.nebulamobil.information.responses.costaresponses.PushTalkResp;
import com.opcode.nebulamobil.utility.JsonConverter;
import com.opcode.nebulamobil.utility.STSLog;
import com.opcode.nebulamobil.utility.UniqueIdCreator;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.ConnectivityManager;
import android.net.DhcpInfo;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;

/**
 * Bu service, ağ üzerinde nebula'nın var olup olmadığını bulmak için kullanılacaktır.
 * Aynı zamanda nebula'dan gelen uygulama bulma taleplerine karşılık verir.
 * @author Okan ERDOĞAN
 *
 * Oct 20, 2014
 */
public class NebulaFinderService extends Service implements NebulaSearchListener{

	/**
	 * Sunucuya udp üzerinden gönderilmek için hazırlanan veriler bu byte dizisi içerisine yerleştirilir.
	 */
	private  byte[] sendData;
	
	/**
	 * Sunucudan okunacak olan veriler bu byte dizisinin içerisine yerleştirilir.
	 */
    private  byte[] receiveData;
    
    /**
     * Veri göndermek ve almak için kullanılacak olan thread'lerdir.
     */
    private Thread sendThread,readThread;
    
    /**
     * Veri almak ve göndermek için kullanılan socket nesnelerine ait referenslardır.
     */
    private static DatagramSocket clientSocket,serverSocket;
    
    /**
     * Sunucuya gönderilmek üzere hazırlanan sendData nesneleri sendPacket içerisine konularak gönderilirler.
     */
    private DatagramPacket sendPacket;
    
    /**
     * Sunucudan elde edilen sunucu bilgileri bu nesne referansında tutulur.
     */
    private AppInfoResp appInfoResp;
    
    /**
     *  Bu değişken searchNebula() metodu çağrıldığı zaman sunucuya AppInfoResp mi AppInfoReq mi gönderileceğini belirler.
     *  Eğer bu değişkenin değeri true ise response gönderilir. Aksi halde request gönderilir.
     */
    private static boolean isResponse=false;
    
    
    public void onCreate(){
		super.onCreate();
		
		// Bu sınıfı nebula search listener yapıyoruz.
		// Bu sayede grafik arayüz üzerinden tara butonuna basıldığı zaman bu service'in searchNebula metodu çalışacaktır.
		ApplicationInfo.setNebulaSearchListener(this);
		
		this.sendData = new byte[30000]; 
		this.receiveData = new byte[30000];
		try {
			clientSocket=new DatagramSocket();
			// ClientSocket broadcast mesajlar gönderilebilmesi için ayarlanıyor.
			clientSocket.setBroadcast(true);
		} catch (SocketException e2) {
			e2.printStackTrace();
		}


		/**
		 *  UDP iletişim protokolü ile bilgi göndermek için kullanılır
		 */
		this.sendThread=new Thread(new Runnable(){

			@Override
			public void run() {
				
				while(true){
					System.out.println("Nebula Finder Döngu-1");

					int i=0;						
						// Buradaki getAppInfo metodu isResponse değişkenine bağlı olarak geriye AppInfoReq veya AppInfoResp döndürür.
						sendData=JsonConverter.toJsonString(getAppInfo()).getBytes();
					
							if(clientSocket!=null){
								
								/**
								 *  Bu bölümde ağdaki bütün ip'lerin 2119 numaralı portuna datagram packet gönderiyoruz.
								 *  Böylece geriye dönen cevaplardan ağda bizimle alakalı kim var kim yok öğrenmiş olucaz.
								 */
								try {
									Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
									
									 while (interfaces.hasMoreElements()) {
										 System.out.println("NebulaFinderNetworkElements:"+i++);
										 NetworkInterface networkInterface = interfaces.nextElement();
										 if (networkInterface.isLoopback() || !networkInterface.isUp()) {
											 continue;
										 }
										 
										 for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
											 InetAddress broadcast = interfaceAddress.getBroadcast();
											 if (broadcast == null) {
												 continue;
											 }	
											 
											 sendPacket =new DatagramPacket(sendData, sendData.length,broadcast,2119); 
											 clientSocket.send(sendPacket);
										 }
									 }
								} catch (IOException e) {
									e.printStackTrace();
								}	
							}						

							synchronized(sendThread){
								// Bu  döngü her çalışmasından sonra bekletilir.
								// Döngüyü bir kez daha çalıştırıp sunucuya veri göndermek için sendThread uyandırılmalıdır.
							try {
								sendThread.wait();
								
							} catch (InterruptedException e) {
								e.printStackTrace();
							}	
							}						
				}
			}
			
		});	
		
		
		/**
		 *  UDP iletişim protokolü ile bilgi almak için kullanılır.
		 */
		this.readThread=new Thread(new Runnable(){

			@Override
			public void run() {
 			
			try {
				System.out.println("ServerSocket oluşturuluyor.");
				// 1425 numaralı portu dinleyerek veri elde etmeye çalışır.
				serverSocket = new DatagramSocket(1425);
			} catch (SocketException e1) {
				e1.printStackTrace();
			} 
			while(true) { 
				System.out.println("Nebula Finder Döngu-2");
					try {
						if(serverSocket!=null){
							
							// Sunucudan elde edilen verilerin doldurulması için bir receivePacket oluşturuluyor.
							// Oluşturulan bu packet serverSocket'e verilerek sunucudan gelen içeriğin bu packete doldurulması sağlanacak.
							DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length); 

							serverSocket.receive(receivePacket);
							
							// Sunucudan okunan içeriğin String olarak karşılığıdır.
							String response = new String(receivePacket.getData(),0,receivePacket.getLength()); 
							
							InetAddress IPAddress = receivePacket.getAddress(); 
							int port = receivePacket.getPort();
							
							JSON jsonObject=(JSON) JsonConverter.fromJsonString(response, JSON.class);
							
							// Eğer sunucudan gelen veri bir AppInfoResp ise bu if şartı çalışır.
							// Böylece sunucuya ait bilgiler elde edilmiş olunur.
							if(jsonObject.getType().equals("AppInfoResp")){
								appInfoResp=(AppInfoResp) JsonConverter.fromJsonString(response, AppInfoResp.class);
								appInfoResp.setIpAddress(IPAddress.getHostAddress());
								appInfoResp.setPort(port);									
								ApplicationInfo.getResponseListener().responseCame(JsonConverter.toJsonString(appInfoResp));

							}
							// Eğer sunucudan gelen veri bir AppInfoReq ise sunucu client'a ait bilgileri elde etmek istiyor demektir.
							// Bu durumda isResponse değişkeni true yapılarak getAppInfo metodunun geriye bir response dönmesi sağlanır.
							// isResponse değişkeninin değeri false olduğu durumlrda getAppInfo metodu geriye her zaman request dönderir.
							else if(jsonObject.getType().equals("AppInfoReq")){
								isResponse=true;
								searchNebula();
							}
						}
					} catch (IOException e) {
						e.printStackTrace();
					} 
					
					} 
			}
		});	
		this.readThread.start();
    }
    
    
	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}


	/**
	 * Bu metod tetiklendiği zaman sendThread çalışmıyorsa çalıştırılır.
	 * Hali hazırda çalışıyorsa uyuyan sendThread kanalı uyandırılır.
	 */
	@Override
	public void searchNebula() {

		if(this.sendThread!=null){
			if (sendThread.getState() == Thread.State.NEW ){
				this.sendThread.start();
				Log.i("NebulaFinderService", "Thread başlatıldı.");	
			}else{
				synchronized (sendThread) {
					sendThread.notifyAll();
					Log.i("NebulaFinderService", "sendThread hali hazırda çalışıyor.");	
				}
					
			}
			
		}
	}
	
	
	/**
	 * Uygulamaya ait verileri talep edenlere verir.
	 * Geriye dönderilecek olan veri AppInfoReq veya AppInfoResp şeklinde olabilir.
	 * Bu iki sınıf tipinden hangisinin geriye gönderileceği isResponse metoduna bağlı olarak değişiklik gösterir.
	 * Eğer sunucu client'a ait verileri elde etmek istiyorsa, isResponse değişkeni true olur ve sunucuya AppInfoResp gönderilir.
	 * Aksi durumda isResponse değişkennin değeri false'tur ve geriye AppInfoReq dönderir.
	 * @return
	 */
	public JSON getAppInfo(){

		WifiManager manager = (WifiManager) this.getSystemService(this.WIFI_SERVICE);
		WifiInfo info = manager.getConnectionInfo();
		String macAddress = info.getMacAddress();
		String versionName="0.0";
		int versionCode=0;
		PackageInfo pInfo;
		String brand = Build.BRAND;
		String model = Build.MODEL;
		try {
			pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
			versionName = pInfo.versionName;
			versionCode=pInfo.versionCode;
			System.out.println();

		} catch (NameNotFoundException e) {
			STSLog.writeLog(e,this.getClass().getCanonicalName(),"Paket ismi bulunamadı.");														
		}
		
		if(isResponse){
			AppInfoResp appInfoResp1=new AppInfoResp();
			appInfoResp1.setMacAddress(macAddress);
			appInfoResp1.setVersionCode(versionCode);
			appInfoResp1.setVersionName(versionName);
			appInfoResp1.setDeviceType(brand+" "+model);
			if(ApplicationInfo.isTablet()){
				appInfoResp1.setAppType("CostaKitchenTerminal");
			}else{
				appInfoResp1.setAppType("CostaHandTerminal");
			}
			appInfoResp1.setType("AppInfoResp");
			appInfoResp1.setResponseId(UniqueIdCreator.createUniqueID());
			isResponse=false;
			return appInfoResp1;
		}else{	
			AppInfoReq appInfoReq=new AppInfoReq();
			appInfoReq.setMacAddress(macAddress);
			appInfoReq.setVersionCode(versionCode);
			appInfoReq.setVersionName(versionName);
			appInfoReq.setDeviceType(brand+" "+model);
			if(ApplicationInfo.isTablet()){
				appInfoReq.setAppType("CostaKitchenTerminal");
			}else{
				appInfoReq.setAppType("CostaHandTerminal");
			}
			appInfoReq.setType("AppInfoReq");
			appInfoReq.setRequestId(UniqueIdCreator.createUniqueID());
			return appInfoReq;
		}		
	}
}
